home *** CD-ROM | disk | FTP | other *** search
/ Ultra Pack / UltraComputing Partner Applications.iso / SunLabs / tclTK / src / tk4.0 / tkText.c < prev    next >
C/C++ Source or Header  |  1995-06-28  |  57KB  |  1,886 lines

  1. /* 
  2.  * tkText.c --
  3.  *
  4.  *    This module provides a big chunk of the implementation of
  5.  *    multi-line editable text widgets for Tk.  Among other things,
  6.  *    it provides the Tcl command interfaces to text widgets and
  7.  *    the display code.  The B-tree representation of text is
  8.  *    implemented elsewhere.
  9.  *
  10.  * Copyright (c) 1992-1994 The Regents of the University of California.
  11.  * Copyright (c) 1994-1995 Sun Microsystems, Inc.
  12.  *
  13.  * See the file "license.terms" for information on usage and redistribution
  14.  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
  15.  */
  16.  
  17. static char sccsid[] = "@(#) tkText.c 1.79 95/06/28 17:19:32";
  18.  
  19. #include "default.h"
  20. #include "tkPort.h"
  21. #include "tkInt.h"
  22. #include "tkText.h"
  23.  
  24. /*
  25.  * Information used to parse text configuration options:
  26.  */
  27.  
  28. static Tk_ConfigSpec configSpecs[] = {
  29.     {TK_CONFIG_BORDER, "-background", "background", "Background",
  30.     DEF_TEXT_BG_COLOR, Tk_Offset(TkText, border), TK_CONFIG_COLOR_ONLY},
  31.     {TK_CONFIG_BORDER, "-background", "background", "Background",
  32.     DEF_TEXT_BG_MONO, Tk_Offset(TkText, border), TK_CONFIG_MONO_ONLY},
  33.     {TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *) NULL,
  34.     (char *) NULL, 0, 0},
  35.     {TK_CONFIG_SYNONYM, "-bg", "background", (char *) NULL,
  36.     (char *) NULL, 0, 0},
  37.     {TK_CONFIG_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
  38.     DEF_TEXT_BORDER_WIDTH, Tk_Offset(TkText, borderWidth), 0},
  39.     {TK_CONFIG_ACTIVE_CURSOR, "-cursor", "cursor", "Cursor",
  40.     DEF_TEXT_CURSOR, Tk_Offset(TkText, cursor), TK_CONFIG_NULL_OK},
  41.     {TK_CONFIG_BOOLEAN, "-exportselection", "exportSelection",
  42.     "ExportSelection", DEF_TEXT_EXPORT_SELECTION,
  43.     Tk_Offset(TkText, exportSelection), 0},
  44.     {TK_CONFIG_SYNONYM, "-fg", "foreground", (char *) NULL,
  45.     (char *) NULL, 0, 0},
  46.     {TK_CONFIG_FONT, "-font", "font", "Font",
  47.     DEF_TEXT_FONT, Tk_Offset(TkText, fontPtr), 0},
  48.     {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
  49.     DEF_TEXT_FG, Tk_Offset(TkText, fgColor), 0},
  50.     {TK_CONFIG_PIXELS, "-height", "height", "Height",
  51.     DEF_TEXT_HEIGHT, Tk_Offset(TkText, height), 0},
  52.     {TK_CONFIG_COLOR, "-highlightbackground", "highlightBackground",
  53.     "HighlightBackground", DEF_TEXT_HIGHLIGHT_BG,
  54.     Tk_Offset(TkText, highlightBgColorPtr), 0},
  55.     {TK_CONFIG_COLOR, "-highlightcolor", "highlightColor", "HighlightColor",
  56.     DEF_TEXT_HIGHLIGHT, Tk_Offset(TkText, highlightColorPtr), 0},
  57.     {TK_CONFIG_PIXELS, "-highlightthickness", "highlightThickness",
  58.     "HighlightThickness",
  59.     DEF_TEXT_HIGHLIGHT_WIDTH, Tk_Offset(TkText, highlightWidth), 0},
  60.     {TK_CONFIG_BORDER, "-insertbackground", "insertBackground", "Foreground",
  61.     DEF_TEXT_INSERT_BG, Tk_Offset(TkText, insertBorder), 0},
  62.     {TK_CONFIG_PIXELS, "-insertborderwidth", "insertBorderWidth", "BorderWidth",
  63.     DEF_TEXT_INSERT_BD_COLOR, Tk_Offset(TkText, insertBorderWidth),
  64.     TK_CONFIG_COLOR_ONLY},
  65.     {TK_CONFIG_PIXELS, "-insertborderwidth", "insertBorderWidth", "BorderWidth",
  66.     DEF_TEXT_INSERT_BD_MONO, Tk_Offset(TkText, insertBorderWidth),
  67.     TK_CONFIG_MONO_ONLY},
  68.     {TK_CONFIG_INT, "-insertofftime", "insertOffTime", "OffTime",
  69.     DEF_TEXT_INSERT_OFF_TIME, Tk_Offset(TkText, insertOffTime), 0},
  70.     {TK_CONFIG_INT, "-insertontime", "insertOnTime", "OnTime",
  71.     DEF_TEXT_INSERT_ON_TIME, Tk_Offset(TkText, insertOnTime), 0},
  72.     {TK_CONFIG_PIXELS, "-insertwidth", "insertWidth", "InsertWidth",
  73.     DEF_TEXT_INSERT_WIDTH, Tk_Offset(TkText, insertWidth), 0},
  74.     {TK_CONFIG_PIXELS, "-padx", "padX", "Pad",
  75.     DEF_TEXT_PADX, Tk_Offset(TkText, padX), 0},
  76.     {TK_CONFIG_PIXELS, "-pady", "padY", "Pad",
  77.     DEF_TEXT_PADY, Tk_Offset(TkText, padY), 0},
  78.     {TK_CONFIG_RELIEF, "-relief", "relief", "Relief",
  79.     DEF_TEXT_RELIEF, Tk_Offset(TkText, relief), 0},
  80.     {TK_CONFIG_BORDER, "-selectbackground", "selectBackground", "Foreground",
  81.     DEF_TEXT_SELECT_COLOR, Tk_Offset(TkText, selBorder),
  82.     TK_CONFIG_COLOR_ONLY},
  83.     {TK_CONFIG_BORDER, "-selectbackground", "selectBackground", "Foreground",
  84.     DEF_TEXT_SELECT_MONO, Tk_Offset(TkText, selBorder),
  85.     TK_CONFIG_MONO_ONLY},
  86.     {TK_CONFIG_STRING, "-selectborderwidth", "selectBorderWidth", "BorderWidth",
  87.     DEF_TEXT_SELECT_BD_COLOR, Tk_Offset(TkText, selBdString),
  88.     TK_CONFIG_COLOR_ONLY|TK_CONFIG_NULL_OK},
  89.     {TK_CONFIG_STRING, "-selectborderwidth", "selectBorderWidth", "BorderWidth",
  90.     DEF_TEXT_SELECT_BD_MONO, Tk_Offset(TkText, selBdString),
  91.     TK_CONFIG_MONO_ONLY|TK_CONFIG_NULL_OK},
  92.     {TK_CONFIG_COLOR, "-selectforeground", "selectForeground", "Background",
  93.     DEF_TEXT_SELECT_FG_COLOR, Tk_Offset(TkText, selFgColorPtr),
  94.     TK_CONFIG_COLOR_ONLY},
  95.     {TK_CONFIG_COLOR, "-selectforeground", "selectForeground", "Background",
  96.     DEF_TEXT_SELECT_FG_MONO, Tk_Offset(TkText, selFgColorPtr),
  97.     TK_CONFIG_MONO_ONLY},
  98.     {TK_CONFIG_BOOLEAN, "-setgrid", "setGrid", "SetGrid",
  99.     DEF_TEXT_SET_GRID, Tk_Offset(TkText, setGrid), 0},
  100.     {TK_CONFIG_PIXELS, "-spacing1", "spacing1", "Spacing",
  101.     DEF_TEXT_SPACING1, Tk_Offset(TkText, spacing1),
  102.     TK_CONFIG_DONT_SET_DEFAULT},
  103.     {TK_CONFIG_PIXELS, "-spacing2", "spacing2", "Spacing",
  104.     DEF_TEXT_SPACING2, Tk_Offset(TkText, spacing2),
  105.     TK_CONFIG_DONT_SET_DEFAULT},
  106.     {TK_CONFIG_PIXELS, "-spacing3", "spacing3", "Spacing",
  107.     DEF_TEXT_SPACING3, Tk_Offset(TkText, spacing3),
  108.     TK_CONFIG_DONT_SET_DEFAULT},
  109.     {TK_CONFIG_UID, "-state", "state", "State",
  110.     DEF_TEXT_STATE, Tk_Offset(TkText, state), 0},
  111.     {TK_CONFIG_STRING, "-tabs", "tabs", "Tabs",
  112.     DEF_TEXT_TABS, Tk_Offset(TkText, tabOptionString), TK_CONFIG_NULL_OK},
  113.     {TK_CONFIG_STRING, "-takefocus", "takeFocus", "TakeFocus",
  114.     DEF_TEXT_TAKE_FOCUS, Tk_Offset(TkText, takeFocus),
  115.     TK_CONFIG_NULL_OK},
  116.     {TK_CONFIG_INT, "-width", "width", "Width",
  117.     DEF_TEXT_WIDTH, Tk_Offset(TkText, width), 0},
  118.     {TK_CONFIG_UID, "-wrap", "wrap", "Wrap",
  119.     DEF_TEXT_WRAP, Tk_Offset(TkText, wrapMode), 0},
  120.     {TK_CONFIG_STRING, "-xscrollcommand", "xScrollCommand", "ScrollCommand",
  121.     DEF_TEXT_XSCROLL_COMMAND, Tk_Offset(TkText, xScrollCmd),
  122.     TK_CONFIG_NULL_OK},
  123.     {TK_CONFIG_STRING, "-yscrollcommand", "yScrollCommand", "ScrollCommand",
  124.     DEF_TEXT_YSCROLL_COMMAND, Tk_Offset(TkText, yScrollCmd),
  125.     TK_CONFIG_NULL_OK},
  126.     {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
  127.     (char *) NULL, 0, 0}
  128. };
  129.  
  130. /*
  131.  * Tk_Uid's used to represent text states:
  132.  */
  133.  
  134. Tk_Uid tkTextCharUid = NULL;
  135. Tk_Uid tkTextDisabledUid = NULL;
  136. Tk_Uid tkTextNoneUid = NULL;
  137. Tk_Uid tkTextNormalUid = NULL;
  138. Tk_Uid tkTextWordUid = NULL;
  139.  
  140. /*
  141.  * Boolean variable indicating whether or not special debugging code
  142.  * should be executed.
  143.  */
  144.  
  145. int tkTextDebug = 0;
  146.  
  147. /*
  148.  * Forward declarations for procedures defined later in this file:
  149.  */
  150.  
  151. static int        ConfigureText _ANSI_ARGS_((Tcl_Interp *interp,
  152.                 TkText *textPtr, int argc, char **argv, int flags));
  153. static int        DeleteChars _ANSI_ARGS_((TkText *textPtr,
  154.                 char *index1String, char *index2String));
  155. static void        DestroyText _ANSI_ARGS_((ClientData clientData));
  156. static void        InsertChars _ANSI_ARGS_((TkText *textPtr,
  157.                 TkTextIndex *indexPtr, char *string));
  158. static void        TextBlinkProc _ANSI_ARGS_((ClientData clientData));
  159. static void        TextCmdDeletedProc _ANSI_ARGS_((
  160.                 ClientData clientData));
  161. static void        TextEventProc _ANSI_ARGS_((ClientData clientData,
  162.                 XEvent *eventPtr));
  163. static int        TextFetchSelection _ANSI_ARGS_((ClientData clientData,
  164.                 int offset, char *buffer, int maxBytes));
  165. static int        TextSearchCmd _ANSI_ARGS_((TkText *textPtr,
  166.                 Tcl_Interp *interp, int argc, char **argv));
  167. static int        TextWidgetCmd _ANSI_ARGS_((ClientData clientData,
  168.                 Tcl_Interp *interp, int argc, char **argv));
  169.  
  170. /*
  171.  *--------------------------------------------------------------
  172.  *
  173.  * Tk_TextCmd --
  174.  *
  175.  *    This procedure is invoked to process the "text" Tcl command.
  176.  *    See the user documentation for details on what it does.
  177.  *
  178.  * Results:
  179.  *    A standard Tcl result.
  180.  *
  181.  * Side effects:
  182.  *    See the user documentation.
  183.  *
  184.  *--------------------------------------------------------------
  185.  */
  186.  
  187. int
  188. Tk_TextCmd(clientData, interp, argc, argv)
  189.     ClientData clientData;    /* Main window associated with
  190.                  * interpreter. */
  191.     Tcl_Interp *interp;        /* Current interpreter. */
  192.     int argc;            /* Number of arguments. */
  193.     char **argv;        /* Argument strings. */
  194. {
  195.     Tk_Window tkwin = (Tk_Window) clientData;
  196.     Tk_Window new;
  197.     register TkText *textPtr;
  198.     TkTextIndex startIndex;
  199.  
  200.     if (argc < 2) {
  201.     Tcl_AppendResult(interp, "wrong # args: should be \"",
  202.         argv[0], " pathName ?options?\"", (char *) NULL);
  203.     return TCL_ERROR;
  204.     }
  205.  
  206.     /*
  207.      * Perform once-only initialization:
  208.      */
  209.  
  210.     if (tkTextNormalUid == NULL) {
  211.     tkTextCharUid = Tk_GetUid("char");
  212.     tkTextDisabledUid = Tk_GetUid("disabled");
  213.     tkTextNoneUid = Tk_GetUid("none");
  214.     tkTextNormalUid = Tk_GetUid("normal");
  215.     tkTextWordUid = Tk_GetUid("word");
  216.     }
  217.  
  218.     /*
  219.      * Create the window.
  220.      */
  221.  
  222.     new = Tk_CreateWindowFromPath(interp, tkwin, argv[1], (char *) NULL);
  223.     if (new == NULL) {
  224.     return TCL_ERROR;
  225.     }
  226.  
  227.     textPtr = (TkText *) ckalloc(sizeof(TkText));
  228.     textPtr->tkwin = new;
  229.     textPtr->display = Tk_Display(new);
  230.     textPtr->interp = interp;
  231.     textPtr->widgetCmd = Tcl_CreateCommand(interp,
  232.         Tk_PathName(textPtr->tkwin), TextWidgetCmd,
  233.         (ClientData) textPtr, TextCmdDeletedProc);
  234.     textPtr->tree = TkBTreeCreate();
  235.     Tcl_InitHashTable(&textPtr->tagTable, TCL_STRING_KEYS);
  236.     textPtr->numTags = 0;
  237.     Tcl_InitHashTable(&textPtr->markTable, TCL_STRING_KEYS);
  238.     Tcl_InitHashTable(&textPtr->windowTable, TCL_STRING_KEYS);
  239.     textPtr->state = tkTextNormalUid;
  240.     textPtr->border = NULL;
  241.     textPtr->borderWidth = 0;
  242.     textPtr->padX = 0;
  243.     textPtr->padY = 0;
  244.     textPtr->relief = TK_RELIEF_FLAT;
  245.     textPtr->highlightWidth = 0;
  246.     textPtr->highlightBgColorPtr = NULL;
  247.     textPtr->highlightColorPtr = NULL;
  248.     textPtr->cursor = None;
  249.     textPtr->fgColor = NULL;
  250.     textPtr->fontPtr = NULL;
  251.     textPtr->charWidth = 1;
  252.     textPtr->spacing1 = 0;
  253.     textPtr->spacing2 = 0;
  254.     textPtr->spacing3 = 0;
  255.     textPtr->tabOptionString = NULL;
  256.     textPtr->tabArrayPtr = NULL;
  257.     textPtr->wrapMode = tkTextCharUid;
  258.     textPtr->width = 0;
  259.     textPtr->height = 0;
  260.     textPtr->setGrid = 0;
  261.     textPtr->prevWidth = Tk_Width(new);
  262.     textPtr->prevHeight = Tk_Height(new);
  263.     TkTextCreateDInfo(textPtr);
  264.     TkTextMakeIndex(textPtr->tree, 0, 0, &startIndex);
  265.     TkTextSetYView(textPtr, &startIndex, 0);
  266.     textPtr->selTagPtr = NULL;
  267.     textPtr->selBorder = NULL;
  268.     textPtr->selBdString = NULL;
  269.     textPtr->selFgColorPtr = NULL;
  270.     textPtr->exportSelection = 1;
  271.     textPtr->abortSelections = 0;
  272.     textPtr->insertMarkPtr = NULL;
  273.     textPtr->insertBorder = NULL;
  274.     textPtr->insertWidth = 0;
  275.     textPtr->insertBorderWidth = 0;
  276.     textPtr->insertOnTime = 0;
  277.     textPtr->insertOffTime = 0;
  278.     textPtr->insertBlinkHandler = (Tk_TimerToken) NULL;
  279.     textPtr->bindingTable = NULL;
  280.     textPtr->currentMarkPtr = NULL;
  281.     textPtr->pickEvent.type = LeaveNotify;
  282.     textPtr->numCurTags = 0;
  283.     textPtr->curTagArrayPtr = NULL;
  284.     textPtr->takeFocus = NULL;
  285.     textPtr->xScrollCmd = NULL;
  286.     textPtr->yScrollCmd = NULL;
  287.     textPtr->flags = 0;
  288.  
  289.     /*
  290.      * Create the "sel" tag and the "current" and "insert" marks.
  291.      */
  292.  
  293.     textPtr->selTagPtr = TkTextCreateTag(textPtr, "sel");
  294.     textPtr->selTagPtr->reliefString = (char *) ckalloc(7);
  295.     strcpy(textPtr->selTagPtr->reliefString, "raised");
  296.     textPtr->selTagPtr->relief = TK_RELIEF_RAISED;
  297.     textPtr->currentMarkPtr = TkTextSetMark(textPtr, "current", &startIndex);
  298.     textPtr->insertMarkPtr = TkTextSetMark(textPtr, "insert", &startIndex);
  299.  
  300.     Tk_SetClass(new, "Text");
  301.     Tk_CreateEventHandler(textPtr->tkwin,
  302.         ExposureMask|StructureNotifyMask|FocusChangeMask,
  303.         TextEventProc, (ClientData) textPtr);
  304.     Tk_CreateEventHandler(textPtr->tkwin, KeyPressMask|KeyReleaseMask
  305.         |ButtonPressMask|ButtonReleaseMask|EnterWindowMask
  306.         |LeaveWindowMask|PointerMotionMask, TkTextBindProc,
  307.         (ClientData) textPtr);
  308.     Tk_CreateSelHandler(textPtr->tkwin, XA_PRIMARY, XA_STRING,
  309.         TextFetchSelection, (ClientData) textPtr, XA_STRING);
  310.     if (ConfigureText(interp, textPtr, argc-2, argv+2, 0) != TCL_OK) {
  311.     Tk_DestroyWindow(textPtr->tkwin);
  312.     return TCL_ERROR;
  313.     }
  314.     interp->result = Tk_PathName(textPtr->tkwin);
  315.  
  316.     return TCL_OK;
  317. }
  318.  
  319. /*
  320.  *--------------------------------------------------------------
  321.  *
  322.  * TextWidgetCmd --
  323.  *
  324.  *    This procedure is invoked to process the Tcl command
  325.  *    that corresponds to a text widget.  See the user
  326.  *    documentation for details on what it does.
  327.  *
  328.  * Results:
  329.  *    A standard Tcl result.
  330.  *
  331.  * Side effects:
  332.  *    See the user documentation.
  333.  *
  334.  *--------------------------------------------------------------
  335.  */
  336.  
  337. static int
  338. TextWidgetCmd(clientData, interp, argc, argv)
  339.     ClientData clientData;    /* Information about text widget. */
  340.     Tcl_Interp *interp;        /* Current interpreter. */
  341.     int argc;            /* Number of arguments. */
  342.     char **argv;        /* Argument strings. */
  343. {
  344.     register TkText *textPtr = (TkText *) clientData;
  345.     int result = TCL_OK;
  346.     size_t length;
  347.     int c;
  348.     TkTextIndex index1, index2;
  349.  
  350.     if (argc < 2) {
  351.     Tcl_AppendResult(interp, "wrong # args: should be \"",
  352.         argv[0], " option ?arg arg ...?\"", (char *) NULL);
  353.     return TCL_ERROR;
  354.     }
  355.     Tk_Preserve((ClientData) textPtr);
  356.     c = argv[1][0];
  357.     length = strlen(argv[1]);
  358.     if ((c == 'b') && (strncmp(argv[1], "bbox", length) == 0)) {
  359.     int x, y, width, height;
  360.  
  361.     if (argc != 3) {
  362.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  363.             argv[0], " bbox index\"", (char *) NULL);
  364.         result = TCL_ERROR;
  365.         goto done;
  366.     }
  367.     if (TkTextGetIndex(interp, textPtr, argv[2], &index1) != TCL_OK) {
  368.         result = TCL_ERROR;
  369.         goto done;
  370.     }
  371.     if (TkTextCharBbox(textPtr, &index1, &x, &y, &width, &height) == 0) {
  372.         sprintf(interp->result, "%d %d %d %d", x, y, width, height);
  373.     }
  374.     } else if ((c == 'c') && (strncmp(argv[1], "cget", length) == 0)
  375.         && (length >= 2)) {
  376.     if (argc != 3) {
  377.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  378.             argv[0], " cget option\"",
  379.             (char *) NULL);
  380.         result = TCL_ERROR;
  381.         goto done;
  382.     }
  383.     result = Tk_ConfigureValue(interp, textPtr->tkwin, configSpecs,
  384.         (char *) textPtr, argv[2], 0);
  385.     } else if ((c == 'c') && (strncmp(argv[1], "compare", length) == 0)
  386.         && (length >= 3)) {
  387.     int relation, value;
  388.     char *p;
  389.  
  390.     if (argc != 5) {
  391.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  392.             argv[0], " compare index1 op index2\"", (char *) NULL);
  393.         result = TCL_ERROR;
  394.         goto done;
  395.     }
  396.     if ((TkTextGetIndex(interp, textPtr, argv[2], &index1) != TCL_OK)
  397.         || (TkTextGetIndex(interp, textPtr, argv[4], &index2)
  398.         != TCL_OK)) {
  399.         result = TCL_ERROR;
  400.         goto done;
  401.     }
  402.     relation = TkTextIndexCmp(&index1, &index2);
  403.     p = argv[3];
  404.     if (p[0] == '<') {
  405.         value = (relation < 0);
  406.         if ((p[1] == '=') && (p[2] == 0)) {
  407.         value = (relation <= 0);
  408.         } else if (p[1] != 0) {
  409.         compareError:
  410.         Tcl_AppendResult(interp, "bad comparison operator \"",
  411.             argv[3], "\": must be <, <=, ==, >=, >, or !=",
  412.             (char *) NULL);
  413.         result = TCL_ERROR;
  414.         goto done;
  415.         }
  416.     } else if (p[0] == '>') {
  417.         value = (relation > 0);
  418.         if ((p[1] == '=') && (p[2] == 0)) {
  419.         value = (relation >= 0);
  420.         } else if (p[1] != 0) {
  421.         goto compareError;
  422.         }
  423.     } else if ((p[0] == '=') && (p[1] == '=') && (p[2] == 0)) {
  424.         value = (relation == 0);
  425.     } else if ((p[0] == '!') && (p[1] == '=') && (p[2] == 0)) {
  426.         value = (relation != 0);
  427.     } else {
  428.         goto compareError;
  429.     }
  430.     interp->result = (value) ? "1" : "0";
  431.     } else if ((c == 'c') && (strncmp(argv[1], "configure", length) == 0)
  432.         && (length >= 3)) {
  433.     if (argc == 2) {
  434.         result = Tk_ConfigureInfo(interp, textPtr->tkwin, configSpecs,
  435.             (char *) textPtr, (char *) NULL, 0);
  436.     } else if (argc == 3) {
  437.         result = Tk_ConfigureInfo(interp, textPtr->tkwin, configSpecs,
  438.             (char *) textPtr, argv[2], 0);
  439.     } else {
  440.         result = ConfigureText(interp, textPtr, argc-2, argv+2,
  441.             TK_CONFIG_ARGV_ONLY);
  442.     }
  443.     } else if ((c == 'd') && (strncmp(argv[1], "debug", length) == 0)
  444.         && (length >= 3)) {
  445.     if (argc > 3) {
  446.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  447.             argv[0], " debug boolean\"", (char *) NULL);
  448.         result = TCL_ERROR;
  449.         goto done;
  450.     }
  451.     if (argc == 2) {
  452.         interp->result = (tkBTreeDebug) ? "1" : "0";
  453.     } else {
  454.         if (Tcl_GetBoolean(interp, argv[2], &tkBTreeDebug) != TCL_OK) {
  455.         result = TCL_ERROR;
  456.         goto done;
  457.         }
  458.         tkTextDebug = tkBTreeDebug;
  459.     }
  460.     } else if ((c == 'd') && (strncmp(argv[1], "delete", length) == 0)
  461.         && (length >= 3)) {
  462.     if ((argc != 3) && (argc != 4)) {
  463.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  464.             argv[0], " delete index1 ?index2?\"", (char *) NULL);
  465.         result = TCL_ERROR;
  466.         goto done;
  467.     }
  468.     if (textPtr->state == tkTextNormalUid) {
  469.         result = DeleteChars(textPtr, argv[2],
  470.             (argc == 4) ? argv[3] : (char *) NULL);
  471.     }
  472.     } else if ((c == 'd') && (strncmp(argv[1], "dlineinfo", length) == 0)
  473.         && (length >= 2)) {
  474.     int x, y, width, height, base;
  475.  
  476.     if (argc != 3) {
  477.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  478.             argv[0], " dlineinfo index\"", (char *) NULL);
  479.         result = TCL_ERROR;
  480.         goto done;
  481.     }
  482.     if (TkTextGetIndex(interp, textPtr, argv[2], &index1) != TCL_OK) {
  483.         result = TCL_ERROR;
  484.         goto done;
  485.     }
  486.     if (TkTextDLineInfo(textPtr, &index1, &x, &y, &width, &height, &base)
  487.         == 0) {
  488.         sprintf(interp->result, "%d %d %d %d %d", x, y, width,
  489.             height, base);
  490.     }
  491.     } else if ((c == 'g') && (strncmp(argv[1], "get", length) == 0)) {
  492.     if ((argc != 3) && (argc != 4)) {
  493.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  494.             argv[0], " get index1 ?index2?\"", (char *) NULL);
  495.         result = TCL_ERROR;
  496.         goto done;
  497.     }
  498.     if (TkTextGetIndex(interp, textPtr, argv[2], &index1) != TCL_OK) {
  499.         result = TCL_ERROR;
  500.         goto done;
  501.     }
  502.     if (argc == 3) {
  503.         index2 = index1;
  504.         TkTextIndexForwChars(&index2, 1, &index2);
  505.     } else if (TkTextGetIndex(interp, textPtr, argv[3], &index2)
  506.         != TCL_OK) {
  507.         result = TCL_ERROR;
  508.         goto done;
  509.     }
  510.     if (TkTextIndexCmp(&index1, &index2) >= 0) {
  511.         goto done;
  512.     }
  513.     while (1) {
  514.         int offset, last, savedChar;
  515.         TkTextSegment *segPtr;
  516.  
  517.         segPtr = TkTextIndexToSeg(&index1, &offset);
  518.         last = segPtr->size;
  519.         if (index1.linePtr == index2.linePtr) {
  520.         int last2;
  521.  
  522.         if (index2.charIndex == index1.charIndex) {
  523.             break;
  524.         }
  525.         last2 = index2.charIndex - index1.charIndex + offset;
  526.         if (last2 < last) {
  527.             last = last2;
  528.         }
  529.         }
  530.         if (segPtr->typePtr == &tkTextCharType) {
  531.         savedChar = segPtr->body.chars[last];
  532.         segPtr->body.chars[last] = 0;
  533.         Tcl_AppendResult(interp, segPtr->body.chars + offset,
  534.             (char *) NULL);
  535.         segPtr->body.chars[last] = savedChar;
  536.         }
  537.         TkTextIndexForwChars(&index1, last-offset, &index1);
  538.     }
  539.     } else if ((c == 'i') && (strncmp(argv[1], "index", length) == 0)
  540.         && (length >= 3)) {
  541.     if (argc != 3) {
  542.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  543.             argv[0], " index index\"",
  544.             (char *) NULL);
  545.         result = TCL_ERROR;
  546.         goto done;
  547.     }
  548.     if (TkTextGetIndex(interp, textPtr, argv[2], &index1) != TCL_OK) {
  549.         result = TCL_ERROR;
  550.         goto done;
  551.     }
  552.     TkTextPrintIndex(&index1, interp->result);
  553.     } else if ((c == 'i') && (strncmp(argv[1], "insert", length) == 0)
  554.         && (length >= 3)) {
  555.     int i, j, numTags;
  556.     char **tagNames;
  557.     TkTextTag **oldTagArrayPtr;
  558.  
  559.     if (argc < 4) {
  560.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  561.             argv[0],
  562.             " insert index chars ?tagList chars tagList ...?\"",
  563.             (char *) NULL);
  564.         result = TCL_ERROR;
  565.         goto done;
  566.     }
  567.     if (TkTextGetIndex(interp, textPtr, argv[2], &index1) != TCL_OK) {
  568.         result = TCL_ERROR;
  569.         goto done;
  570.     }
  571.     if (textPtr->state == tkTextNormalUid) {
  572.         for (j = 3;  j < argc; j += 2) {
  573.         InsertChars(textPtr, &index1, argv[j]);
  574.         if (argc > (j+1)) {
  575.             TkTextIndexForwChars(&index1, (int) strlen(argv[j]),
  576.                 &index2);
  577.             oldTagArrayPtr = TkBTreeGetTags(&index1, &numTags);
  578.             if (oldTagArrayPtr != NULL) {
  579.             for (i = 0; i < numTags; i++) {
  580.                 TkBTreeTag(&index1, &index2, oldTagArrayPtr[i], 0);
  581.             }
  582.             ckfree((char *) oldTagArrayPtr);
  583.             }
  584.             if (Tcl_SplitList(interp, argv[j+1], &numTags, &tagNames)
  585.                 != TCL_OK) {
  586.             result = TCL_ERROR;
  587.             goto done;
  588.             }
  589.             for (i = 0; i < numTags; i++) {
  590.             TkBTreeTag(&index1, &index2,
  591.                 TkTextCreateTag(textPtr, tagNames[i]), 1);
  592.             }
  593.             ckfree((char *) tagNames);
  594.             index1 = index2;
  595.         }
  596.         }
  597.     }
  598.     } else if ((c == 'm') && (strncmp(argv[1], "mark", length) == 0)) {
  599.     result = TkTextMarkCmd(textPtr, interp, argc, argv);
  600.     } else if ((c == 's') && (strcmp(argv[1], "scan") == 0) && (length >= 2)) {
  601.     result = TkTextScanCmd(textPtr, interp, argc, argv);
  602.     } else if ((c == 's') && (strcmp(argv[1], "search") == 0)
  603.         && (length >= 3)) {
  604.     result = TextSearchCmd(textPtr, interp, argc, argv);
  605.     } else if ((c == 's') && (strcmp(argv[1], "see") == 0) && (length >= 3)) {
  606.     result = TkTextSeeCmd(textPtr, interp, argc, argv);
  607.     } else if ((c == 't') && (strcmp(argv[1], "tag") == 0)) {
  608.     result = TkTextTagCmd(textPtr, interp, argc, argv);
  609.     } else if ((c == 'w') && (strncmp(argv[1], "window", length) == 0)) {
  610.     result = TkTextWindowCmd(textPtr, interp, argc, argv);
  611.     } else if ((c == 'x') && (strncmp(argv[1], "xview", length) == 0)) {
  612.     result = TkTextXviewCmd(textPtr, interp, argc, argv);
  613.     } else if ((c == 'y') && (strncmp(argv[1], "yview", length) == 0)
  614.         && (length >= 2)) {
  615.     result = TkTextYviewCmd(textPtr, interp, argc, argv);
  616.     } else {
  617.     Tcl_AppendResult(interp, "bad option \"", argv[1],
  618.         "\":  must be bbox, cget, compare, configure, debug, delete, ",
  619.         "dlineinfo, get, index, insert, mark, scan, search, see, ",
  620.         "tag, window, xview, or yview",
  621.         (char *) NULL);
  622.     result = TCL_ERROR;
  623.     }
  624.  
  625.     done:
  626.     Tk_Release((ClientData) textPtr);
  627.     return result;
  628. }
  629.  
  630. /*
  631.  *----------------------------------------------------------------------
  632.  *
  633.  * DestroyText --
  634.  *
  635.  *    This procedure is invoked by Tk_EventuallyFree or Tk_Release
  636.  *    to clean up the internal structure of a text at a safe time
  637.  *    (when no-one is using it anymore).
  638.  *
  639.  * Results:
  640.  *    None.
  641.  *
  642.  * Side effects:
  643.  *    Everything associated with the text is freed up.
  644.  *
  645.  *----------------------------------------------------------------------
  646.  */
  647.  
  648. static void
  649. DestroyText(clientData)
  650.     ClientData clientData;    /* Info about text widget. */
  651. {
  652.     register TkText *textPtr = (TkText *) clientData;
  653.     Tcl_HashSearch search;
  654.     Tcl_HashEntry *hPtr;
  655.     TkTextTag *tagPtr;
  656.  
  657.     /*
  658.      * Free up all the stuff that requires special handling, then
  659.      * let Tk_FreeOptions handle all the standard option-related
  660.      * stuff.  Special note:  free up display-related information
  661.      * before deleting the B-tree, since display-related stuff
  662.      * may refer to stuff in the B-tree.
  663.      */
  664.  
  665.     TkTextFreeDInfo(textPtr);
  666.     TkBTreeDestroy(textPtr->tree);
  667.     for (hPtr = Tcl_FirstHashEntry(&textPtr->tagTable, &search);
  668.         hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
  669.     tagPtr = (TkTextTag *) Tcl_GetHashValue(hPtr);
  670.     TkTextFreeTag(textPtr, tagPtr);
  671.     }
  672.     Tcl_DeleteHashTable(&textPtr->tagTable);
  673.     for (hPtr = Tcl_FirstHashEntry(&textPtr->markTable, &search);
  674.         hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
  675.     ckfree((char *) Tcl_GetHashValue(hPtr));
  676.     }
  677.     Tcl_DeleteHashTable(&textPtr->markTable);
  678.     if (textPtr->tabArrayPtr != NULL) {
  679.     ckfree((char *) textPtr->tabArrayPtr);
  680.     }
  681.     if (textPtr->insertBlinkHandler != NULL) {
  682.     Tk_DeleteTimerHandler(textPtr->insertBlinkHandler);
  683.     }
  684.     if (textPtr->bindingTable != NULL) {
  685.     Tk_DeleteBindingTable(textPtr->bindingTable);
  686.     }
  687.  
  688.     /*
  689.      * NOTE: do NOT free up selBorder, selBdString, or selFgColorPtr:
  690.      * they are duplicates of information in the "sel" tag, which was
  691.      * freed up as part of deleting the tags above.
  692.      */
  693.  
  694.     textPtr->selBorder = NULL;
  695.     textPtr->selBdString = NULL;
  696.     textPtr->selFgColorPtr = NULL;
  697.     Tk_FreeOptions(configSpecs, (char *) textPtr, textPtr->display, 0);
  698.     ckfree((char *) textPtr);
  699. }
  700.  
  701. /*
  702.  *----------------------------------------------------------------------
  703.  *
  704.  * ConfigureText --
  705.  *
  706.  *    This procedure is called to process an argv/argc list, plus
  707.  *    the Tk option database, in order to configure (or
  708.  *    reconfigure) a text widget.
  709.  *
  710.  * Results:
  711.  *    The return value is a standard Tcl result.  If TCL_ERROR is
  712.  *    returned, then interp->result contains an error message.
  713.  *
  714.  * Side effects:
  715.  *    Configuration information, such as text string, colors, font,
  716.  *    etc. get set for textPtr;  old resources get freed, if there
  717.  *    were any.
  718.  *
  719.  *----------------------------------------------------------------------
  720.  */
  721.  
  722. static int
  723. ConfigureText(interp, textPtr, argc, argv, flags)
  724.     Tcl_Interp *interp;        /* Used for error reporting. */
  725.     register TkText *textPtr;    /* Information about widget;  may or may
  726.                  * not already have values for some fields. */
  727.     int argc;            /* Number of valid entries in argv. */
  728.     char **argv;        /* Arguments. */
  729.     int flags;            /* Flags to pass to Tk_ConfigureWidget. */
  730. {
  731.     int oldExport = textPtr->exportSelection;
  732.     int charHeight;
  733.  
  734.     if (Tk_ConfigureWidget(interp, textPtr->tkwin, configSpecs,
  735.         argc, argv, (char *) textPtr, flags) != TCL_OK) {
  736.     return TCL_ERROR;
  737.     }
  738.  
  739.     /*
  740.      * A few other options also need special processing, such as parsing
  741.      * the geometry and setting the background from a 3-D border.
  742.      */
  743.  
  744.     if ((textPtr->state != tkTextNormalUid)
  745.         && (textPtr->state != tkTextDisabledUid)) {
  746.     Tcl_AppendResult(interp, "bad state value \"", textPtr->state,
  747.         "\":  must be normal or disabled", (char *) NULL);
  748.     textPtr->state = tkTextNormalUid;
  749.     return TCL_ERROR;
  750.     }
  751.  
  752.     if ((textPtr->wrapMode != tkTextCharUid)
  753.         && (textPtr->wrapMode != tkTextNoneUid)
  754.         && (textPtr->wrapMode != tkTextWordUid)) {
  755.     Tcl_AppendResult(interp, "bad wrap mode \"", textPtr->wrapMode,
  756.         "\":  must be char, none, or word", (char *) NULL);
  757.     textPtr->wrapMode = tkTextCharUid;
  758.     return TCL_ERROR;
  759.     }
  760.  
  761.     Tk_SetBackgroundFromBorder(textPtr->tkwin, textPtr->border);
  762.  
  763.     /*
  764.      * Don't allow negative spacings.
  765.      */
  766.  
  767.     if (textPtr->spacing1 < 0) {
  768.     textPtr->spacing1 = 0;
  769.     }
  770.     if (textPtr->spacing2 < 0) {
  771.     textPtr->spacing2 = 0;
  772.     }
  773.     if (textPtr->spacing3 < 0) {
  774.     textPtr->spacing3 = 0;
  775.     }
  776.  
  777.     /*
  778.      * Parse tab stops.
  779.      */
  780.  
  781.     if (textPtr->tabArrayPtr != NULL) {
  782.     ckfree((char *) textPtr->tabArrayPtr);
  783.     textPtr->tabArrayPtr = NULL;
  784.     }
  785.     if (textPtr->tabOptionString != NULL) {
  786.     textPtr->tabArrayPtr = TkTextGetTabs(interp, textPtr->tkwin,
  787.         textPtr->tabOptionString);
  788.     if (textPtr->tabArrayPtr == NULL) {
  789.         Tcl_AddErrorInfo(interp,"\n    (while processing -tabs option)");
  790.         return TCL_ERROR;
  791.     }
  792.     }
  793.  
  794.     /*
  795.      * Make sure that configuration options are properly mirrored
  796.      * between the widget record and the "sel" tags.  NOTE: we don't
  797.      * have to free up information during the mirroring;  old
  798.      * information was freed when it was replaced in the widget
  799.      * record.
  800.      */
  801.  
  802.     textPtr->selTagPtr->border = textPtr->selBorder;
  803.     if (textPtr->selTagPtr->bdString != textPtr->selBdString) {
  804.     textPtr->selTagPtr->bdString = textPtr->selBdString;
  805.     if (textPtr->selBdString != NULL) {
  806.         if (Tk_GetPixels(interp, textPtr->tkwin, textPtr->selBdString,
  807.             &textPtr->selTagPtr->borderWidth) != TCL_OK) {
  808.         return TCL_ERROR;
  809.         }
  810.         if (textPtr->selTagPtr->borderWidth < 0) {
  811.         textPtr->selTagPtr->borderWidth = 0;
  812.         }
  813.     }
  814.     }
  815.     textPtr->selTagPtr->fgColor = textPtr->selFgColorPtr;
  816.     textPtr->selTagPtr->affectsDisplay = 0;
  817.     if ((textPtr->selTagPtr->border != NULL)
  818.         || (textPtr->selTagPtr->bdString != NULL)
  819.         || (textPtr->selTagPtr->reliefString != NULL)
  820.         || (textPtr->selTagPtr->bgStipple != None)
  821.         || (textPtr->selTagPtr->fgColor != NULL)
  822.         || (textPtr->selTagPtr->fontPtr != None)
  823.         || (textPtr->selTagPtr->fgStipple != None)
  824.         || (textPtr->selTagPtr->justifyString != NULL)
  825.         || (textPtr->selTagPtr->lMargin1String != NULL)
  826.         || (textPtr->selTagPtr->lMargin2String != NULL)
  827.         || (textPtr->selTagPtr->offsetString != NULL)
  828.         || (textPtr->selTagPtr->overstrikeString != NULL)
  829.         || (textPtr->selTagPtr->rMarginString != NULL)
  830.         || (textPtr->selTagPtr->spacing1String != NULL)
  831.         || (textPtr->selTagPtr->spacing2String != NULL)
  832.         || (textPtr->selTagPtr->spacing3String != NULL)
  833.         || (textPtr->selTagPtr->tabString != NULL)
  834.         || (textPtr->selTagPtr->underlineString != NULL)
  835.         || (textPtr->selTagPtr->wrapMode != NULL)) {
  836.     textPtr->selTagPtr->affectsDisplay = 1;
  837.     }
  838.     TkTextRedrawTag(textPtr, (TkTextIndex *) NULL, (TkTextIndex *) NULL,
  839.         textPtr->selTagPtr, 1);
  840.  
  841.     /*
  842.      * Claim the selection if we've suddenly started exporting it and there
  843.      * are tagged characters.
  844.      */
  845.  
  846.     if (textPtr->exportSelection && (!oldExport)) {
  847.     TkTextSearch search;
  848.     TkTextIndex first, last;
  849.  
  850.     TkTextMakeIndex(textPtr->tree, 0, 0, &first);
  851.     TkTextMakeIndex(textPtr->tree,
  852.         TkBTreeNumLines(textPtr->tree), 0, &last);
  853.     TkBTreeStartSearch(&first, &last, textPtr->selTagPtr, &search);
  854.     if (TkBTreeCharTagged(&first, textPtr->selTagPtr)
  855.         || TkBTreeNextTag(&search)) {
  856.         Tk_OwnSelection(textPtr->tkwin, XA_PRIMARY, TkTextLostSelection,
  857.             (ClientData) textPtr);
  858.         textPtr->flags |= GOT_SELECTION;
  859.     }
  860.     }
  861.  
  862.     /*
  863.      * Register the desired geometry for the window, and arrange for
  864.      * the window to be redisplayed.
  865.      */
  866.  
  867.     if (textPtr->width <= 0) {
  868.     textPtr->width = 1;
  869.     }
  870.     if (textPtr->height <= 0) {
  871.     textPtr->height = 1;
  872.     }
  873.     textPtr->charWidth = XTextWidth(textPtr->fontPtr, "0", 1);
  874.     if (textPtr->charWidth <= 0) {
  875.     textPtr->charWidth = 1;
  876.     }
  877.     charHeight = (textPtr->fontPtr->ascent + textPtr->fontPtr->descent);
  878.     Tk_GeometryRequest(textPtr->tkwin,
  879.         textPtr->width * textPtr->charWidth + 2*textPtr->borderWidth
  880.             + 2*textPtr->padX + 2*textPtr->highlightWidth,
  881.         textPtr->height * charHeight + 2*textPtr->borderWidth
  882.             + 2*textPtr->padY + 2*textPtr->highlightWidth);
  883.     Tk_SetInternalBorder(textPtr->tkwin,
  884.         textPtr->borderWidth + textPtr->highlightWidth);
  885.     if (textPtr->setGrid) {
  886.     Tk_SetGrid(textPtr->tkwin, textPtr->width, textPtr->height,
  887.         textPtr->charWidth, charHeight);
  888.     } else {
  889.     Tk_UnsetGrid(textPtr->tkwin);
  890.     }
  891.  
  892.     TkTextRelayoutWindow(textPtr);
  893.     return TCL_OK;
  894. }
  895.  
  896. /*
  897.  *--------------------------------------------------------------
  898.  *
  899.  * TextEventProc --
  900.  *
  901.  *    This procedure is invoked by the Tk dispatcher on
  902.  *    structure changes to a text.  For texts with 3D
  903.  *    borders, this procedure is also invoked for exposures.
  904.  *
  905.  * Results:
  906.  *    None.
  907.  *
  908.  * Side effects:
  909.  *    When the window gets deleted, internal structures get
  910.  *    cleaned up.  When it gets exposed, it is redisplayed.
  911.  *
  912.  *--------------------------------------------------------------
  913.  */
  914.  
  915. static void
  916. TextEventProc(clientData, eventPtr)
  917.     ClientData clientData;    /* Information about window. */
  918.     register XEvent *eventPtr;    /* Information about event. */
  919. {
  920.     register TkText *textPtr = (TkText *) clientData;
  921.     TkTextIndex index, index2;
  922.  
  923.     if (eventPtr->type == Expose) {
  924.     TkTextRedrawRegion(textPtr, eventPtr->xexpose.x,
  925.         eventPtr->xexpose.y, eventPtr->xexpose.width,
  926.         eventPtr->xexpose.height);
  927.     } else if (eventPtr->type == ConfigureNotify) {
  928.     if ((textPtr->prevWidth != Tk_Width(textPtr->tkwin))
  929.         || (textPtr->prevHeight != Tk_Height(textPtr->tkwin))) {
  930.         TkTextRelayoutWindow(textPtr);
  931.         textPtr->prevWidth = Tk_Width(textPtr->tkwin);
  932.         textPtr->prevHeight = Tk_Height(textPtr->tkwin);
  933.     }
  934.     } else if (eventPtr->type == DestroyNotify) {
  935.     if (textPtr->setGrid) {
  936.         Tk_UnsetGrid(textPtr->tkwin);
  937.     }
  938.     if (textPtr->tkwin != NULL) {
  939.         textPtr->tkwin = NULL;
  940.         Tcl_DeleteCommand(textPtr->interp,
  941.             Tcl_GetCommandName(textPtr->interp,
  942.             textPtr->widgetCmd));
  943.     }
  944.     Tk_EventuallyFree((ClientData) textPtr, DestroyText);
  945.     } else if ((eventPtr->type == FocusIn) || (eventPtr->type == FocusOut)) {
  946.     if (eventPtr->xfocus.detail != NotifyInferior) {
  947.         Tk_DeleteTimerHandler(textPtr->insertBlinkHandler);
  948.         if (eventPtr->type == FocusIn) {
  949.         textPtr->flags |= GOT_FOCUS | INSERT_ON;
  950.         if (textPtr->insertOffTime != 0) {
  951.             textPtr->insertBlinkHandler = Tk_CreateTimerHandler(
  952.                 textPtr->insertOnTime, TextBlinkProc,
  953.                 (ClientData) textPtr);
  954.         }
  955.         } else {
  956.         textPtr->flags &= ~(GOT_FOCUS | INSERT_ON);
  957.         textPtr->insertBlinkHandler = (Tk_TimerToken) NULL;
  958.         }
  959.         TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index);
  960.         TkTextIndexForwChars(&index, 1, &index2);
  961.         TkTextChanged(textPtr, &index, &index2);
  962.         if (textPtr->highlightWidth > 0) {
  963.         TkTextRedrawRegion(textPtr, 0, 0, textPtr->highlightWidth,
  964.             textPtr->highlightWidth);
  965.         }
  966.     }
  967.     }
  968. }
  969.  
  970. /*
  971.  *----------------------------------------------------------------------
  972.  *
  973.  * TextCmdDeletedProc --
  974.  *
  975.  *    This procedure is invoked when a widget command is deleted.  If
  976.  *    the widget isn't already in the process of being destroyed,
  977.  *    this command destroys it.
  978.  *
  979.  * Results:
  980.  *    None.
  981.  *
  982.  * Side effects:
  983.  *    The widget is destroyed.
  984.  *
  985.  *----------------------------------------------------------------------
  986.  */
  987.  
  988. static void
  989. TextCmdDeletedProc(clientData)
  990.     ClientData clientData;    /* Pointer to widget record for widget. */
  991. {
  992.     TkText *textPtr = (TkText *) clientData;
  993.     Tk_Window tkwin = textPtr->tkwin;
  994.  
  995.     /*
  996.      * This procedure could be invoked either because the window was
  997.      * destroyed and the command was then deleted (in which case tkwin
  998.      * is NULL) or because the command was deleted, and then this procedure
  999.      * destroys the widget.
  1000.      */
  1001.  
  1002.     if (tkwin != NULL) {
  1003.     textPtr->tkwin = NULL;
  1004.     Tk_DestroyWindow(tkwin);
  1005.     }
  1006. }
  1007.  
  1008. /*
  1009.  *----------------------------------------------------------------------
  1010.  *
  1011.  * InsertChars --
  1012.  *
  1013.  *    This procedure implements most of the functionality of the
  1014.  *    "insert" widget command.
  1015.  *
  1016.  * Results:
  1017.  *    None.
  1018.  *
  1019.  * Side effects:
  1020.  *    The characters in "string" get added to the text just before
  1021.  *    the character indicated by "indexPtr".
  1022.  *
  1023.  *----------------------------------------------------------------------
  1024.  */
  1025.  
  1026. static void
  1027. InsertChars(textPtr, indexPtr, string)
  1028.     TkText *textPtr;        /* Overall information about text widget. */
  1029.     TkTextIndex *indexPtr;    /* Where to insert new characters.  May be
  1030.                  * modified and/or invalidated. */
  1031.     char *string;        /* Null-terminated string containing new
  1032.                  * information to add to text. */
  1033. {
  1034.     int lineIndex;
  1035.  
  1036.     /*
  1037.      * Don't allow insertions on the last (dummy) line of the text.
  1038.      */
  1039.  
  1040.     lineIndex = TkBTreeLineIndex(indexPtr->linePtr);
  1041.     if (lineIndex == TkBTreeNumLines(textPtr->tree)) {
  1042.     lineIndex--;
  1043.     TkTextMakeIndex(textPtr->tree, lineIndex, 1000000, indexPtr);
  1044.     }
  1045.  
  1046.     /*
  1047.      * Notify the display module that lines are about to change, then do
  1048.      * the insertion.
  1049.      */
  1050.  
  1051.     TkTextChanged(textPtr, indexPtr, indexPtr);
  1052.     TkBTreeInsertChars(indexPtr, string);
  1053.  
  1054.     /*
  1055.      * Invalidate any selection retrievals in progress.
  1056.      */
  1057.  
  1058.     textPtr->abortSelections = 1;
  1059. }
  1060.  
  1061. /*
  1062.  *----------------------------------------------------------------------
  1063.  *
  1064.  * DeleteChars --
  1065.  *
  1066.  *    This procedure implements most of the functionality of the
  1067.  *    "delete" widget command.
  1068.  *
  1069.  * Results:
  1070.  *    Returns a standard Tcl result, and leaves an error message
  1071.  *    in textPtr->interp if there is an error.
  1072.  *
  1073.  * Side effects:
  1074.  *    Characters get deleted from the text.
  1075.  *
  1076.  *----------------------------------------------------------------------
  1077.  */
  1078.  
  1079. static int
  1080. DeleteChars(textPtr, index1String, index2String)
  1081.     TkText *textPtr;        /* Overall information about text widget. */
  1082.     char *index1String;        /* String describing location of first
  1083.                  * character to delete. */
  1084.     char *index2String;        /* String describing location of last
  1085.                  * character to delete.  NULL means just
  1086.                  * delete the one character given by
  1087.                  * index1String. */
  1088. {
  1089.     int line1, line2, line, charIndex, resetView;
  1090.     TkTextIndex index1, index2;
  1091.  
  1092.     /*
  1093.      * Parse the starting and stopping indices.
  1094.      */
  1095.  
  1096.     if (TkTextGetIndex(textPtr->interp, textPtr, index1String, &index1)
  1097.         != TCL_OK) {
  1098.     return TCL_ERROR;
  1099.     }
  1100.     if (index2String != NULL) {
  1101.     if (TkTextGetIndex(textPtr->interp, textPtr, index2String, &index2)
  1102.         != TCL_OK) {
  1103.         return TCL_ERROR;
  1104.     }
  1105.     } else {
  1106.     index2 = index1;
  1107.     TkTextIndexForwChars(&index2, 1, &index2);
  1108.     }
  1109.  
  1110.     /*
  1111.      * Make sure there's really something to delete.
  1112.      */
  1113.  
  1114.     if (TkTextIndexCmp(&index1, &index2) >= 0) {
  1115.     return TCL_OK;
  1116.     }
  1117.  
  1118.     /*
  1119.      * The code below is ugly, but it's needed to make sure there
  1120.      * is always a dummy empty line at the end of the text.  If the
  1121.      * final newline of the file (just before the dummy line) is being
  1122.      * deleted, then back up index to just before the newline.  If
  1123.      * there is a newline just before the first character being deleted,
  1124.      * then back up the first index too, so that an even number of lines
  1125.      * gets deleted.  Furthermore, remove any tags that are present on
  1126.      * the newline that isn't going to be deleted after all (this simulates
  1127.      * deleting the newline and then adding a "clean" one back again).
  1128.      */
  1129.  
  1130.     line1 = TkBTreeLineIndex(index1.linePtr);
  1131.     line2 = TkBTreeLineIndex(index2.linePtr);
  1132.     if (line2 == TkBTreeNumLines(textPtr->tree)) {
  1133.     TkTextTag **arrayPtr;
  1134.     int arraySize, i;
  1135.     TkTextIndex oldIndex2;
  1136.  
  1137.     oldIndex2 = index2;
  1138.     TkTextIndexBackChars(&oldIndex2, 1, &index2);
  1139.     line2--;
  1140.     if ((index1.charIndex == 0) && (line1 != 0)) {
  1141.         TkTextIndexBackChars(&index1, 1, &index1);
  1142.         line1--;
  1143.     }
  1144.     arrayPtr = TkBTreeGetTags(&index2, &arraySize);
  1145.     if (arrayPtr != NULL) {
  1146.         for (i = 0; i < arraySize; i++) {
  1147.         TkBTreeTag(&index2, &oldIndex2, arrayPtr[i], 0);
  1148.         }
  1149.         ckfree((char *) arrayPtr);
  1150.     }
  1151.     }
  1152.  
  1153.     /*
  1154.      * Tell the display what's about to happen so it can discard
  1155.      * obsolete display information, then do the deletion.  Also,
  1156.      * if the deletion involves the top line on the screen, then
  1157.      * we have to reset the view (the deletion will invalidate
  1158.      * textPtr->topIndex).  Compute what the new first character
  1159.      * will be, then do the deletion, then reset the view.
  1160.      */
  1161.  
  1162.     TkTextChanged(textPtr, &index1, &index2);
  1163.     resetView = line = charIndex = 0;
  1164.     if (TkTextIndexCmp(&index2, &textPtr->topIndex) >= 0) {
  1165.     if (TkTextIndexCmp(&index1, &textPtr->topIndex) <= 0) {
  1166.         /*
  1167.          * Deletion range straddles topIndex: use the beginning
  1168.          * of the range as the new topIndex.
  1169.          */
  1170.  
  1171.         resetView = 1;
  1172.         line = line1;
  1173.         charIndex = index1.charIndex;
  1174.     } else if (index1.linePtr == textPtr->topIndex.linePtr) {
  1175.         /*
  1176.          * Deletion range starts on top line but after topIndex.
  1177.          * Use the current topIndex as the new one.
  1178.          */
  1179.  
  1180.         resetView = 1;
  1181.         line = line1;
  1182.         charIndex = textPtr->topIndex.charIndex;
  1183.     }
  1184.     } else if (index2.linePtr == textPtr->topIndex.linePtr) {
  1185.     /*
  1186.      * Deletion range ends on top line but before topIndex.
  1187.      * Figure out what will be the new character index for
  1188.      * the character currently pointed to by topIndex.
  1189.      */
  1190.  
  1191.     resetView = 1;
  1192.     line = line2;
  1193.     charIndex = textPtr->topIndex.charIndex;
  1194.     if (index1.linePtr != index2.linePtr) {
  1195.         charIndex -= index2.charIndex;
  1196.     } else {
  1197.         charIndex -= (index2.charIndex - index1.charIndex);
  1198.     }
  1199.     }
  1200.     TkBTreeDeleteChars(&index1, &index2);
  1201.     if (resetView) {
  1202.     TkTextMakeIndex(textPtr->tree, line, charIndex, &index1);
  1203.     TkTextSetYView(textPtr, &index1, 0);
  1204.     }
  1205.  
  1206.     /*
  1207.      * Invalidate any selection retrievals in progress.
  1208.      */
  1209.  
  1210.     textPtr->abortSelections = 1;
  1211.  
  1212.     return TCL_OK;
  1213. }
  1214.  
  1215. /*
  1216.  *----------------------------------------------------------------------
  1217.  *
  1218.  * TextFetchSelection --
  1219.  *
  1220.  *    This procedure is called back by Tk when the selection is
  1221.  *    requested by someone.  It returns part or all of the selection
  1222.  *    in a buffer provided by the caller.
  1223.  *
  1224.  * Results:
  1225.  *    The return value is the number of non-NULL bytes stored
  1226.  *    at buffer.  Buffer is filled (or partially filled) with a
  1227.  *    NULL-terminated string containing part or all of the selection,
  1228.  *    as given by offset and maxBytes.
  1229.  *
  1230.  * Side effects:
  1231.  *    None.
  1232.  *
  1233.  *----------------------------------------------------------------------
  1234.  */
  1235.  
  1236. static int
  1237. TextFetchSelection(clientData, offset, buffer, maxBytes)
  1238.     ClientData clientData;        /* Information about text widget. */
  1239.     int offset;                /* Offset within selection of first
  1240.                      * character to be returned. */
  1241.     char *buffer;            /* Location in which to place
  1242.                      * selection. */
  1243.     int maxBytes;            /* Maximum number of bytes to place
  1244.                      * at buffer, not including terminating
  1245.                      * NULL character. */
  1246. {
  1247.     register TkText *textPtr = (TkText *) clientData;
  1248.     TkTextIndex eof;
  1249.     int count, chunkSize, offsetInSeg;
  1250.     TkTextSearch search;
  1251.     TkTextSegment *segPtr;
  1252.  
  1253.     if (!textPtr->exportSelection) {
  1254.     return -1;
  1255.     }
  1256.  
  1257.     /*
  1258.      * Find the beginning of the next range of selected text.  Note:  if
  1259.      * the selection is being retrieved in multiple pieces (offset != 0)
  1260.      * and some modification has been made to the text that affects the
  1261.      * selection then reject the selection request (make 'em start over
  1262.      * again).
  1263.      */
  1264.  
  1265.     if (offset == 0) {
  1266.     TkTextMakeIndex(textPtr->tree, 0, 0, &textPtr->selIndex);
  1267.     textPtr->abortSelections = 0;
  1268.     } else if (textPtr->abortSelections) {
  1269.     return 0;
  1270.     }
  1271.     TkTextMakeIndex(textPtr->tree, TkBTreeNumLines(textPtr->tree), 0, &eof);
  1272.     TkBTreeStartSearch(&textPtr->selIndex, &eof, textPtr->selTagPtr, &search);
  1273.     if (!TkBTreeCharTagged(&textPtr->selIndex, textPtr->selTagPtr)) {
  1274.     if (!TkBTreeNextTag(&search)) {
  1275.         if (offset == 0) {
  1276.         return -1;
  1277.         } else {
  1278.         return 0;
  1279.         }
  1280.     }
  1281.     textPtr->selIndex = search.curIndex;
  1282.     }
  1283.  
  1284.     /*
  1285.      * Each iteration through the outer loop below scans one selected range.
  1286.      * Each iteration through the inner loop scans one segment in the
  1287.      * selected range.
  1288.      */
  1289.  
  1290.     count = 0;
  1291.     while (1) {
  1292.     /*
  1293.      * Find the end of the current range of selected text.
  1294.      */
  1295.  
  1296.     if (!TkBTreeNextTag(&search)) {
  1297.         panic("TextFetchSelection couldn't find end of range");
  1298.     }
  1299.  
  1300.     /*
  1301.      * Copy information from character segments into the buffer
  1302.      * until either we run out of space in the buffer or we get
  1303.      * to the end of this range of text.
  1304.      */
  1305.  
  1306.     while (1) {
  1307.         if (maxBytes == 0) {
  1308.         goto done;
  1309.         }
  1310.         segPtr = TkTextIndexToSeg(&textPtr->selIndex, &offsetInSeg);
  1311.         chunkSize = segPtr->size - offsetInSeg;
  1312.         if (chunkSize > maxBytes) {
  1313.         chunkSize = maxBytes;
  1314.         }
  1315.         if (textPtr->selIndex.linePtr == search.curIndex.linePtr) {
  1316.         int leftInRange;
  1317.  
  1318.         leftInRange = search.curIndex.charIndex
  1319.             - textPtr->selIndex.charIndex;
  1320.         if (leftInRange < chunkSize) {
  1321.             chunkSize = leftInRange;
  1322.             if (chunkSize <= 0) {
  1323.             break;
  1324.             }
  1325.         }
  1326.         }
  1327.         if (segPtr->typePtr == &tkTextCharType) {
  1328.         memcpy((VOID *) buffer, (VOID *) (segPtr->body.chars
  1329.             + offsetInSeg), (size_t) chunkSize);
  1330.         buffer += chunkSize;
  1331.         maxBytes -= chunkSize;
  1332.         count += chunkSize;
  1333.         }
  1334.         TkTextIndexForwChars(&textPtr->selIndex, chunkSize,
  1335.             &textPtr->selIndex);
  1336.     }
  1337.  
  1338.     /*
  1339.      * Find the beginning of the next range of selected text.
  1340.      */
  1341.  
  1342.     if (!TkBTreeNextTag(&search)) {
  1343.         break;
  1344.     }
  1345.     textPtr->selIndex = search.curIndex;
  1346.     }
  1347.  
  1348.     done:
  1349.     *buffer = 0;
  1350.     return count;
  1351. }
  1352.  
  1353. /*
  1354.  *----------------------------------------------------------------------
  1355.  *
  1356.  * TkTextLostSelection --
  1357.  *
  1358.  *    This procedure is called back by Tk when the selection is
  1359.  *    grabbed away from a text widget.
  1360.  *
  1361.  * Results:
  1362.  *    None.
  1363.  *
  1364.  * Side effects:
  1365.  *    The "sel" tag is cleared from the window.
  1366.  *
  1367.  *----------------------------------------------------------------------
  1368.  */
  1369.  
  1370. void
  1371. TkTextLostSelection(clientData)
  1372.     ClientData clientData;        /* Information about text widget. */
  1373. {
  1374.     register TkText *textPtr = (TkText *) clientData;
  1375.     TkTextIndex start, end;
  1376.  
  1377.     if (!textPtr->exportSelection) {
  1378.     return;
  1379.     }
  1380.  
  1381.     /*
  1382.      * Just remove the "sel" tag from everything in the widget.
  1383.      */
  1384.  
  1385.     TkTextMakeIndex(textPtr->tree, 0, 0, &start);
  1386.     TkTextMakeIndex(textPtr->tree, TkBTreeNumLines(textPtr->tree), 0, &end);
  1387.     TkTextRedrawTag(textPtr, &start, &end, textPtr->selTagPtr, 1);
  1388.     TkBTreeTag(&start, &end, textPtr->selTagPtr, 0);
  1389.     textPtr->flags &= ~GOT_SELECTION;
  1390. }
  1391.  
  1392. /*
  1393.  *----------------------------------------------------------------------
  1394.  *
  1395.  * TextBlinkProc --
  1396.  *
  1397.  *    This procedure is called as a timer handler to blink the
  1398.  *    insertion cursor off and on.
  1399.  *
  1400.  * Results:
  1401.  *    None.
  1402.  *
  1403.  * Side effects:
  1404.  *    The cursor gets turned on or off, redisplay gets invoked,
  1405.  *    and this procedure reschedules itself.
  1406.  *
  1407.  *----------------------------------------------------------------------
  1408.  */
  1409.  
  1410. static void
  1411. TextBlinkProc(clientData)
  1412.     ClientData clientData;    /* Pointer to record describing text. */
  1413. {
  1414.     register TkText *textPtr = (TkText *) clientData;
  1415.     TkTextIndex index, index2;
  1416.  
  1417.     if (!(textPtr->flags & GOT_FOCUS) || (textPtr->insertOffTime == 0)) {
  1418.     return;
  1419.     }
  1420.     if (textPtr->flags & INSERT_ON) {
  1421.     textPtr->flags &= ~INSERT_ON;
  1422.     textPtr->insertBlinkHandler = Tk_CreateTimerHandler(
  1423.         textPtr->insertOffTime, TextBlinkProc, (ClientData) textPtr);
  1424.     } else {
  1425.     textPtr->flags |= INSERT_ON;
  1426.     textPtr->insertBlinkHandler = Tk_CreateTimerHandler(
  1427.         textPtr->insertOnTime, TextBlinkProc, (ClientData) textPtr);
  1428.     }
  1429.     TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index);
  1430.     TkTextIndexForwChars(&index, 1, &index2);
  1431.     TkTextChanged(textPtr, &index, &index2);
  1432. }
  1433.  
  1434. /*
  1435.  *----------------------------------------------------------------------
  1436.  *
  1437.  * TextSearchCmd --
  1438.  *
  1439.  *    This procedure is invoked to process the "search" widget command
  1440.  *    for text widgets.  See the user documentation for details on what
  1441.  *    it does.
  1442.  *
  1443.  * Results:
  1444.  *    A standard Tcl result.
  1445.  *
  1446.  * Side effects:
  1447.  *    See the user documentation.
  1448.  *
  1449.  *----------------------------------------------------------------------
  1450.  */
  1451.  
  1452. static int
  1453. TextSearchCmd(textPtr, interp, argc, argv)
  1454.     TkText *textPtr;        /* Information about text widget. */
  1455.     Tcl_Interp *interp;        /* Current interpreter. */
  1456.     int argc;            /* Number of arguments. */
  1457.     char **argv;        /* Argument strings. */
  1458. {
  1459.     int backwards, exact, c, i, argsLeft, noCase, leftToScan;
  1460.     size_t length;
  1461.     int numLines, startingLine, startingChar, lineNum, firstChar, lastChar;
  1462.     int code, matchLength, matchChar, passes, stopLine, searchWholeText;
  1463.     int patLength;
  1464.     char *arg, *pattern, *varName, *p, *startOfLine;
  1465.     char buffer[20];
  1466.     TkTextIndex index, stopIndex;
  1467.     Tcl_DString line, patDString;
  1468.     TkTextSegment *segPtr;
  1469.     TkTextLine *linePtr;
  1470.     Tcl_RegExp regexp = NULL;        /* Initialization needed only to
  1471.                      * prevent compiler warning. */
  1472.  
  1473.     /*
  1474.      * Parse switches and other arguments.
  1475.      */
  1476.  
  1477.     exact = 1;
  1478.     backwards = 0;
  1479.     noCase = 0;
  1480.     varName = NULL;
  1481.     for (i = 2; i < argc; i++) {
  1482.     arg = argv[i];
  1483.     if (arg[0] != '-') {
  1484.         break;
  1485.     }
  1486.     length = strlen(arg);
  1487.     if (length < 2) {
  1488.         badSwitch:
  1489.         Tcl_AppendResult(interp, "bad switch \"", arg,
  1490.             "\": must be -forward, -backward, -exact, -regexp, ",
  1491.             "-nocase, -count, or --", (char *) NULL);
  1492.         return TCL_ERROR;
  1493.     }
  1494.     c = arg[1];
  1495.     if ((c == 'b') && (strncmp(argv[i], "-backwards", length) == 0)) {
  1496.         backwards = 1;
  1497.     } else if ((c == 'c') && (strncmp(argv[i], "-count", length) == 0)) {
  1498.         if (i >= (argc-1)) {
  1499.         interp->result = "no value given for \"-count\" option";
  1500.         return TCL_ERROR;
  1501.         }
  1502.         i++;
  1503.         varName = argv[i];
  1504.     } else if ((c == 'e') && (strncmp(argv[i], "-exact", length) == 0)) {
  1505.         exact = 1;
  1506.     } else if ((c == 'f') && (strncmp(argv[i], "-forwards", length) == 0)) {
  1507.         backwards = 0;
  1508.     } else if ((c == 'n') && (strncmp(argv[i], "-nocase", length) == 0)) {
  1509.         noCase = 1;
  1510.     } else if ((c == 'r') && (strncmp(argv[i], "-regexp", length) == 0)) {
  1511.         exact = 0;
  1512.     } else if ((c == '-') && (strncmp(argv[i], "--", length) == 0)) {
  1513.         i++;
  1514.         break;
  1515.     } else {
  1516.         goto badSwitch;
  1517.     }
  1518.     }
  1519.     argsLeft = argc - (i+2);
  1520.     if ((argsLeft != 0) && (argsLeft != 1)) {
  1521.     Tcl_AppendResult(interp, "wrong # args: should be \"",
  1522.         argv[0], " search ?switches? pattern index ?stopIndex?",
  1523.         (char *) NULL);
  1524.     return TCL_ERROR;
  1525.     }
  1526.     pattern = argv[i];
  1527.  
  1528.     /*
  1529.      * Convert the pattern to lower-case if we're supposed to ignore case.
  1530.      */
  1531.  
  1532.     if (noCase) {
  1533.     Tcl_DStringInit(&patDString);
  1534.     Tcl_DStringAppend(&patDString, pattern, -1);
  1535.     pattern = Tcl_DStringValue(&patDString);
  1536.     for (p = pattern; *p != 0; p++) {
  1537.         if (isupper(UCHAR(*p))) {
  1538.         *p = tolower(UCHAR(*p));
  1539.         }
  1540.     }
  1541.     }
  1542.  
  1543.     if (TkTextGetIndex(interp, textPtr, argv[i+1], &index) != TCL_OK) {
  1544.     return TCL_ERROR;
  1545.     }
  1546.     numLines = TkBTreeNumLines(textPtr->tree);
  1547.     startingLine = TkBTreeLineIndex(index.linePtr);
  1548.     startingChar = index.charIndex;
  1549.     if (startingLine >= numLines) {
  1550.     startingLine = 0;
  1551.     startingChar = 0;
  1552.     }
  1553.     if (argsLeft == 1) {
  1554.     if (TkTextGetIndex(interp, textPtr, argv[i+2], &stopIndex) != TCL_OK) {
  1555.         return TCL_ERROR;
  1556.     }
  1557.     stopLine = TkBTreeLineIndex(stopIndex.linePtr);
  1558.     if (!backwards && (stopLine == numLines)) {
  1559.         stopLine = numLines-1;
  1560.     }
  1561.     searchWholeText = 0;
  1562.     } else {
  1563.     stopLine = 0;
  1564.     searchWholeText = 1;
  1565.     }
  1566.  
  1567.     /*
  1568.      * Scan through all of the lines of the text circularly, starting
  1569.      * at the given index.
  1570.      */
  1571.  
  1572.     matchLength = patLength = 0;    /* Only needed to prevent compiler
  1573.                      * warnings. */
  1574.     if (exact) {
  1575.     patLength = strlen(pattern);
  1576.     } else {
  1577.     regexp = Tcl_RegExpCompile(interp, pattern);
  1578.     if (regexp == NULL) {
  1579.         return TCL_ERROR;
  1580.     }
  1581.     }
  1582.     lineNum = startingLine;
  1583.     code = TCL_OK;
  1584.     Tcl_DStringInit(&line);
  1585.     for (passes = 0; passes < 2; ) {
  1586.     if (lineNum >= numLines) {
  1587.         /*
  1588.          * Don't search the dummy last line of the text.
  1589.          */
  1590.  
  1591.         goto nextLine;
  1592.     }
  1593.  
  1594.     /*
  1595.      * Extract the text from the line.  If we're doing regular
  1596.      * expression matching, drop the newline from the line, so
  1597.      * that "$" can be used to match the end of the line.
  1598.      */
  1599.  
  1600.     linePtr = TkBTreeFindLine(textPtr->tree, lineNum);
  1601.     for (segPtr = linePtr->segPtr; segPtr != NULL;
  1602.         segPtr = segPtr->nextPtr) {
  1603.         if (segPtr->typePtr != &tkTextCharType) {
  1604.         continue;
  1605.         }
  1606.         Tcl_DStringAppend(&line, segPtr->body.chars, segPtr->size);
  1607.     }
  1608.     if (!exact) {
  1609.         Tcl_DStringSetLength(&line, Tcl_DStringLength(&line)-1);
  1610.     }
  1611.     startOfLine = Tcl_DStringValue(&line);
  1612.  
  1613.     /*
  1614.      * If we're ignoring case, convert the line to lower case.
  1615.      */
  1616.  
  1617.     if (noCase) {
  1618.         for (p = Tcl_DStringValue(&line); *p != 0; p++) {
  1619.         if (isupper(UCHAR(*p))) {
  1620.             *p = tolower(UCHAR(*p));
  1621.         }
  1622.         }
  1623.     }
  1624.  
  1625.     /*
  1626.      * Check for matches within the current line.  If so, and if we're
  1627.      * searching backwards, repeat the search to find the last match
  1628.      * in the line.
  1629.      */
  1630.  
  1631.     matchChar = -1;
  1632.     firstChar = 0;
  1633.     lastChar = INT_MAX;
  1634.     if (lineNum == startingLine) {
  1635.         /*
  1636.          * The starting line is tricky: the first time we see it
  1637.          * we check one part of the line, and the second pass through
  1638.          * we check the other part of the line.
  1639.          */
  1640.  
  1641.         passes++;
  1642.         if (passes == 1) {
  1643.         if (backwards) {
  1644.             lastChar = startingChar;
  1645.         } else {
  1646.             firstChar = startingChar;
  1647.             if (firstChar >= Tcl_DStringLength(&line)) {
  1648.             goto nextLine;
  1649.             }
  1650.         }
  1651.         } else {
  1652.         if (backwards) {
  1653.             firstChar = startingChar;
  1654.         } else {
  1655.             lastChar = startingChar;
  1656.         }
  1657.         }
  1658.     }
  1659.     do {
  1660.         int thisLength;
  1661.         if (exact) {
  1662.         p = strstr(startOfLine + firstChar, pattern);
  1663.         if (p == NULL) {
  1664.             break;
  1665.         }
  1666.         i = p - startOfLine;
  1667.         thisLength = patLength;
  1668.         } else {
  1669.         char *start, *end;
  1670.         int match;
  1671.  
  1672.         match = Tcl_RegExpExec(interp, regexp,
  1673.             startOfLine + firstChar, startOfLine);
  1674.         if (match < 0) {
  1675.             code = TCL_ERROR;
  1676.             goto done;
  1677.         }
  1678.         if (!match) {
  1679.             break;
  1680.         }
  1681.         Tcl_RegExpRange(regexp, 0, &start, &end);
  1682.         i = start - startOfLine;
  1683.         thisLength = end - start;
  1684.         }
  1685.         if (i >= lastChar) {
  1686.         break;
  1687.         }
  1688.         matchChar = i;
  1689.         matchLength = thisLength;
  1690.         firstChar = matchChar+1;
  1691.     } while (backwards);
  1692.  
  1693.     /*
  1694.      * If we found a match then we're done.  Make sure that
  1695.      * the match occurred before the stopping index, if one was
  1696.      * specified.
  1697.      */
  1698.  
  1699.     if (matchChar >= 0) {
  1700.         /*
  1701.          * The index information returned by the regular expression
  1702.          * parser only considers textual information:  it doesn't
  1703.          * account for embedded windows or any other non-textual info.
  1704.          * Scan through the line's segments again to adjust both
  1705.          * matchChar and matchCount.
  1706.          */
  1707.  
  1708.         for (segPtr = linePtr->segPtr, leftToScan = matchChar;
  1709.             leftToScan >= 0; segPtr = segPtr->nextPtr) {
  1710.         if (segPtr->typePtr != &tkTextCharType) {
  1711.             matchChar += segPtr->size;
  1712.             continue;
  1713.         }
  1714.         leftToScan -= segPtr->size;
  1715.         }
  1716.         for (leftToScan += matchLength; leftToScan > 0;
  1717.             segPtr = segPtr->nextPtr) {
  1718.         if (segPtr->typePtr != &tkTextCharType) {
  1719.             matchLength += segPtr->size;
  1720.             continue;
  1721.         }
  1722.         leftToScan -= segPtr->size;
  1723.         }
  1724.         TkTextMakeIndex(textPtr->tree, lineNum, matchChar, &index);
  1725.         if (!searchWholeText) {
  1726.         if (!backwards && (TkTextIndexCmp(&index, &stopIndex) >= 0)) {
  1727.             goto done;
  1728.         }
  1729.         if (backwards && (TkTextIndexCmp(&index, &stopIndex) < 0)) {
  1730.             goto done;
  1731.         }
  1732.         }
  1733.         if (varName != NULL) {
  1734.         sprintf(buffer, "%d", matchLength);
  1735.         if (Tcl_SetVar(interp, varName, buffer, TCL_LEAVE_ERR_MSG)
  1736.             == NULL) {
  1737.             code = TCL_ERROR;
  1738.             goto done;
  1739.         }
  1740.         }
  1741.         TkTextPrintIndex(&index, interp->result);
  1742.         goto done;
  1743.     }
  1744.  
  1745.     /*
  1746.      * Go to the next (or previous) line;
  1747.      */
  1748.  
  1749.     nextLine:
  1750.     if (backwards) {
  1751.         lineNum--;
  1752.         if (!searchWholeText) {
  1753.         if (lineNum < stopLine) {
  1754.             break;
  1755.         }
  1756.         } else if (lineNum < 0) {
  1757.         lineNum = numLines-1;
  1758.         }
  1759.     } else {
  1760.         lineNum++;
  1761.         if (!searchWholeText) {
  1762.         if (lineNum > stopLine) {
  1763.             break;
  1764.         }
  1765.         } else if (lineNum >= numLines) {
  1766.         lineNum = 0;
  1767.         }
  1768.     }
  1769.     Tcl_DStringSetLength(&line, 0);
  1770.     }
  1771.     done:
  1772.     Tcl_DStringFree(&line);
  1773.     if (noCase) {
  1774.     Tcl_DStringFree(&patDString);
  1775.     }
  1776.     return code;
  1777. }
  1778.  
  1779. /*
  1780.  *----------------------------------------------------------------------
  1781.  *
  1782.  * TkTextGetTabs --
  1783.  *
  1784.  *    Parses a string description of a set of tab stops.
  1785.  *
  1786.  * Results:
  1787.  *    The return value is a pointer to a malloc'ed structure holding
  1788.  *    parsed information about the tab stops.  If an error occurred
  1789.  *    then the return value is NULL and an error message is left in
  1790.  *    interp->result.
  1791.  *
  1792.  * Side effects:
  1793.  *    Memory is allocated for the structure that is returned.  It is
  1794.  *    up to the caller to free this structure when it is no longer
  1795.  *    needed.
  1796.  *
  1797.  *----------------------------------------------------------------------
  1798.  */
  1799.  
  1800. TkTextTabArray *
  1801. TkTextGetTabs(interp, tkwin, string)
  1802.     Tcl_Interp *interp;            /* Used for error reporting. */
  1803.     Tk_Window tkwin;            /* Window in which the tabs will be
  1804.                      * used. */
  1805.     char *string;            /* Description of the tab stops.  See
  1806.                      * the text manual entry for details. */
  1807. {
  1808.     int argc, i, count, c;
  1809.     char **argv;
  1810.     TkTextTabArray *tabArrayPtr;
  1811.     TkTextTab *tabPtr;
  1812.  
  1813.     if (Tcl_SplitList(interp, string, &argc, &argv) != TCL_OK) {
  1814.     return NULL;
  1815.     }
  1816.  
  1817.     /*
  1818.      * First find out how many entries we need to allocate in the
  1819.      * tab array.
  1820.      */
  1821.  
  1822.     count = 0;
  1823.     for (i = 0; i < argc; i++) {
  1824.     c = argv[i][0];
  1825.     if ((c != 'l') && (c != 'r') && (c != 'c') && (c != 'n')) {
  1826.         count++;
  1827.     }
  1828.     }
  1829.  
  1830.     /*
  1831.      * Parse the elements of the list one at a time to fill in the
  1832.      * array.
  1833.      */
  1834.  
  1835.     tabArrayPtr = (TkTextTabArray *) ckalloc((unsigned)
  1836.         (sizeof(TkTextTabArray) + (count-1)*sizeof(TkTextTab)));
  1837.     tabArrayPtr->numTabs = 0;
  1838.     for (i = 0, tabPtr = &tabArrayPtr->tabs[0]; i  < argc; i++, tabPtr++) {
  1839.     if (Tk_GetPixels(interp, tkwin, argv[i], &tabPtr->location)
  1840.         != TCL_OK) {
  1841.         goto error;
  1842.     }
  1843.     tabArrayPtr->numTabs++;
  1844.  
  1845.     /*
  1846.      * See if there is an explicit alignment in the next list
  1847.      * element.  Otherwise just use "left".
  1848.      */
  1849.  
  1850.     tabPtr->alignment = LEFT;
  1851.     if ((i+1) == argc) {
  1852.         continue;
  1853.     }
  1854.     c = UCHAR(argv[i+1][0]);
  1855.     if (!isalpha(c)) {
  1856.         continue;
  1857.     }
  1858.     i += 1;
  1859.     if ((c == 'l') && (strncmp(argv[i], "left",
  1860.         strlen(argv[i])) == 0)) {
  1861.         tabPtr->alignment = LEFT;
  1862.     } else if ((c == 'r') && (strncmp(argv[i], "right",
  1863.         strlen(argv[i])) == 0)) {
  1864.         tabPtr->alignment = RIGHT;
  1865.     } else if ((c == 'c') && (strncmp(argv[i], "center",
  1866.         strlen(argv[i])) == 0)) {
  1867.         tabPtr->alignment = CENTER;
  1868.     } else if ((c == 'n') && (strncmp(argv[i],
  1869.         "numeric", strlen(argv[i])) == 0)) {
  1870.         tabPtr->alignment = NUMERIC;
  1871.     } else {
  1872.         Tcl_AppendResult(interp, "bad tab alignment \"",
  1873.             argv[i], "\": must be left, right, center, or numeric",
  1874.             (char *) NULL);
  1875.         goto error;
  1876.     }
  1877.     }
  1878.     ckfree((char *) argv);
  1879.     return tabArrayPtr;
  1880.  
  1881.     error:
  1882.     ckfree((char *) tabArrayPtr);
  1883.     ckfree((char *) argv);
  1884.     return NULL;
  1885. }
  1886.